--
-- LANES.LUA
--
-- Multithreading and -core support for Lua
--
-- Author: Asko Kauppi <akauppi@gmail.com>
--
-- History:
--    Jun-08 AKa: major revise
--    15-May-07 AKa: pthread_join():less version, some speedup & ability to
--                   handle more threads (~ 8000-9000, up from ~ 5000)
--    26-Feb-07 AKa: serialization working (C side)
--    17-Sep-06 AKa: started the module (serialization)
--
--[[
===============================================================================

Copyright (C) 2007-08 Asko Kauppi <akauppi@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

===============================================================================
]]--

module( "lanes", package.seeall )

require "lua51-lanes"
assert( type(lanes)=="table" )

local mm= lanes

local linda_new=    assert( mm.linda_new )

local thread_new=   assert(mm.thread_new)
local thread_state= assert(mm.thread_state)
local thread_wait=  assert(mm.thread_wait)
local thread_cancel= assert(mm.thread_cancel)

local _yield= assert(mm._yield)
local _single= assert(mm._single)

local _version= assert(mm._version)

ABOUT= 
{
    author= "Asko Kauppi <akauppi@gmail.com>",
    description= "Running multiple Lua states in parallel",
    license= "MIT/X11",
    copyright= "Copyright (c) 2007-08, Asko Kauppi",
    version= _version,
}


---=== Misc ===---

-- Test & debug
--
yield= assert(_yield)


---=== Laning ===---

-- lane_h[1..n]: lane results, same as via 'lanes_h:unpack()'
-- lane_h[0]:    can be read to make sure a thread has finished (always gives 'true')
-- lane_h[-1]:   error message, without propagating the error
--
--      Reading a Lane result (or [0]) propagates a possible error in the lane
--      (and execution does not return). Cancelled lanes give 'nil' values.
--
-- lane_h.state: "pending"/"running"/"waiting"/"done"/"error"/"cancelled"
--
-- lane_h.error: error value (normally string) if lane has met an error
--               nil if not in "error" state
--
local lane_mt= {
    __index= function( me, k )
                if type(k) == "number" then
                    -- 'me[0]=true' marks we've already taken in the results
                    if not rawget( me, 0 ) then
                        -- Wait indefinately; either propagates the substate's
                        -- error, or returns "done"/"cancelled"
                        --
                        local t= { thread_wait(me._ud,-1) }   -- wait indefinate
                        local st= assert(t[1])  -- "done"/"error"/"cancelled"

                        me[0]= true  -- marker, even on errors

                        if st=="done" then
                            -- Use 'pairs' and not 'ipairs' so that nil holes in
                            -- the returned values are tolerated.
                            --
                            for i,v in pairs(t) do
                                if i~=1 then
                                    me[i-1]= v
                                end
                            end
                        elseif st=="error" then
                            me[-1]= assert(t[2])    -- error value
                        end
                    end
                    
                    -- Check errors even if we'd first peeked them via [-1]
                    -- (or '.error') and then came for the actual results.
                    --
                    if rawget(me, -1) and k~=-1 then
                        error( t[2], 3 )    -- level that takes to customer's code
                                            -- TBD: level does not seem to have effect!
                    end
                    return rawget( me, k )  -- avoid recursion!
                    --
                elseif k=="state" then     -- me.state
                    return thread_state(me._ud)
                    --
                elseif k=="error" then     -- me.state
                    local st= thread_state(me._ud)
                    if st=="error" then
                        return me[-1]
                    end
                    -- return nil
                else
                    error( "Unknown key: "..k )
                end
             end
    }

-----
-- h= lanes.new [libs_str|opt_tbl [...]] ( lane_func ) ( [...] )
--
-- The calling convention is best served via examples:
--
--  lanes.new "io,math" { globals={ A=10 }, cancelstep=true } ( function() ... end )
--  lanes.new "table"( function() ... end )
--  lanes.new( function() ... end )
--
-- It allows 'modifiers' of the basic 'lanes.new()' call, and such can be queued
-- to gather the exact setup one wishes to use.
--
-- The order of the string and table modifiers is any, and they can be
-- appended (a second 'libs_str' parameter does not overwrite the first).
--
-- 'libs': nil:     no libraries available (default)
--         "":      only base library ('assert', 'print', 'unpack' etc.)
--         "math,os": math + os + base libraries (named ones + base)
--         "*":     all standard libraries available
--
-- 'opt': .priority:  int (-2..+2) smaller is lower priority (0 = default)
--
--	      .cancelstep: bool | uint
--            false: cancellation check only at pending Linda operations
--                   (send/receive) so no runtime performance penalty (default)
--            true:  adequate cancellation check (same as 100)
--            >0:    cancellation check every x Lua lines (small number= faster
--                   reaction but more performance overhead)
--
--        .globals:  table of globals to set for a new thread (passed by value)
--
--        .lazy:    'true' for starting the lane only once (if) it's results
--                  are ever actually required. 'false' (default) starts it
--                  onto the background.
--
--        ... (more options may be introduced later) ...
--
-- Calling with a function parameter ('lane_func') ends the string/table
-- modifiers, and prepares a lane generator. One can either finish here,
-- and call the generator later (maybe multiple times, with different parameters) 
-- or add on actual thread arguments to also ignite the thread on the same call.
--
local final_new

function new( a, ... )
    local opt= {}
    local libs= nil
    local lev= 2  -- level for errors

    -- Note: need to name the function, so we can refer to it at the end.
    --
    local function g( a )
        lev= lev+1
        local t= type(a)
        if t=="string" then
            libs= libs and (libs..","..a) or a
        elseif t=="table" then
            for k,v in pairs(a) do opt[k]= v end
        elseif t=="function" then
            return final_new( libs, opt, a, lev+1 )
        else
            error( "bad chained value: "..tostring(a), lev )
        end
        return g    -- chain on :)
    end
    return g(a)
end

local function lane_proxy( ud )
    local proxy= {
        _ud= ud,
        cancel= function(me) thread_cancel(ud) end,       -- void= me:cancel()
        
        -- ':unpack' gives an error if the thread had caused one (intentional)
        --
        unpack= function(me,wait) -- [...]= me:unpack( [wait_secs=-1] )
                    local t= { thread_wait(ud,wait) }
                    if t[1]=="done" then
                        return unpack(t,2)
                    end
                    -- return nil (timeout, or cancelled)
                end,
        }
    assert( proxy._ud )
    setmetatable( proxy, lane_mt )

    return proxy
end

local valid_libs= {
    ["package"]= true,
    ["table"]= true,
    ["io"]= true,
    ["os"]= true,
    ["string"]= true,
    ["math"]= true,
    ["debug"]= true,
    --
    ["base"]= true,
    ["coroutine"]= true,
    ["*"]= true
}

-----
-- Note: Within this function, use 'lev' at 'error' calls, to let the actual
--       location of original 'lanes.new()' call take the blame.
--
final_new= function( libs, opt, func, lev )
    --
    local prio, cs, g_tbl, lazy

    -- Check 'libs' already here, so the error will go to right place
    --
    if libs then
        for s in string.gmatch(libs, "[%a*]+") do
            if not valid_libs[s] then
                error( "Bad library name: "..s, lev )
            end
        end
    end

    for k,v in pairs(opt) do
            if k=="priority" then prio= v
        elseif k=="cancelstep" then cs= (v==true) and 100 or
                                        (v==false) and 0 or 
                                        type(v)=="number" and v or
                                        error( "Bad cancelstep: "..tostring(v), lev )
        elseif k=="globals" then g_tbl= v
        elseif k=="lazy" then lazy= v
        --..
        elseif k==1 then error( "unkeyed option: ".. tostring(v), lev )
        else error( "Bad option: ".. tostring(k), lev )
        end
    end

    -- TBD: We should implement 'lazy' completely within the Lua side.
    --
    if lazy then
        error( "TBD: 'lazy' option not implemented, yet!", lev )
    end

    -- Lane generator
    --
    return function(...)
              return lane_proxy( thread_new( func, libs, cs, prio, g_tbl,
                                             ... ) )     -- args
           end
end


---=== Lindas ===---

-- We let the C code attach methods to userdata directly

-----
-- linda_ud= lanes.linda()
--
linda= linda_new


--the end
